rm(list=ls())
library(tidyverse)
library(tseries)
library(forecast)
library(TSA)
library(dplyr)

Load and inspect data

# source: https://streeteasy.com/blog/data-dashboard/?agg=Total&metric=Inventory&type=Rentals&bedrooms=Any%20Bedrooms&property=Any%20Property%20Type&minDate=2010-01-01&maxDate=2022-12-01&area=Flatiron,Brooklyn%20Heights

# note that we can also get breakdown by bedrooms if we want

# set up generalizable path without local path hard coded
base_path <- system('git rev-parse --show-toplevel', intern = T)

# rental index - one for each of four real boroughs
# note: we won't use the rental index as it is already smoothed via ARIMA
rental_index_raw <- read.csv(paste0(base_path, '/data/rentalIndex_All.csv'))
# median asking rent - four real boroughs + 155 neighborhoods
median_rent_raw <- read.csv(paste0(base_path, '/data/medianAskingRent_All.csv'))
# rental inventory - four real boroughs + 155 neighborhoods
inventory_raw <- read.csv(paste0(base_path, '/data/rentalInventory_All.csv'))
rental_index_manhattan <- ts(rental_index_raw$Manhattan,
                             start=c(2007,1),
                             frequency=12)

tsdisplay(rental_index_manhattan)


median_rent_manhattan <- median_rent_raw %>% 
  filter(areaName=="Manhattan") %>%
  unlist() %>% as.numeric() %>% 
  na.omit() %>% 
  ts(start=c(2010,1),frequency=12)
Warning: NAs introduced by coercion
tsdisplay(median_rent_manhattan)


inventory_manhattan <- inventory_raw %>% 
  filter(areaName=="Manhattan") %>% 
  unlist() %>% as.numeric() %>% 
  na.omit() %>%
  ts(start=c(2010,1),frequency=12)
Warning: NAs introduced by coercion
tsdisplay(inventory_manhattan)


rent_train <- window(median_rent_manhattan, start=2010, end=c(2020,1))
inventory_train <- window(inventory_manhattan, start=2010, end=c(2020,1))
rent_test <- window(median_rent_manhattan, start=c(2020,2), end=c(2022,12))
inventory_test <- window(inventory_manhattan, start=c(2020,2), end=c(2022,12))

# Box-Cox transformed
tsdisplay(BoxCox(rent_train,lambda='auto'))

tsdisplay(BoxCox(inventory_train,lambda='auto'))


# Median Rent
autoplot(median_rent_manhattan, main = "Median Rent - Manhattan", ylab="Median Rent ($)", xlab="Year") + 
  theme_classic() +
  theme(legend.position="none") +
  scale_y_continuous(breaks=c(seq(3000,4200,400))) + 
  geom_hline(yintercept=seq(2800,4200, by=200), size=0.1, linetype=2)

ggsave("rent_raw_series.png", width=128, height=96, units="mm")


# Inventory
autoplot(inventory_manhattan, main = "Inventory - Manhattan", ylab="Inventory", xlab="Year") + 
  theme_classic() +
  theme(legend.position="none") +
  scale_y_continuous(breaks=c(seq(10000,40000,10000))) + 
  geom_hline(yintercept=seq(10000,40000, by=10000), size=0.1, linetype=2)

ggsave("inventory_raw_series.png", width=128, height=96, units="mm")

#save(inventory_manhattan,file='data/inventory_manhattan.RData')
#save(median_rent_manhattan,file='data/median_rent_manhattan.RData')
plot((rent_train-mean(rent_train))/var(rent_train)/30)
lines((inventory_train-mean(inventory_train))/var(inventory_train),col='red')

rent_stationary <- diff(BoxCox(rent_train,lambda=0),differences=1,lag=1)
inventory_stationary <- diff(BoxCox(inventory_train,lambda=0),differences=1,lag=1)

plot((rent_stationary-mean(rent_stationary))/var(rent_stationary))
lines(((inventory_stationary-mean(inventory_stationary)))/var(inventory_stationary),col='red')

SARIMA

# median rent SARIMA
rent_lambda = BoxCox.lambda(rent_train)
rent_sarima <- auto.arima(rent_train,lambda=rent_lambda,trace=TRUE)

 ARIMA(2,1,2)(1,0,1)[12] with drift         : Inf
 ARIMA(0,1,0)            with drift         : 3141.802
 ARIMA(1,1,0)(1,0,0)[12] with drift         : 3128.999
 ARIMA(0,1,1)(0,0,1)[12] with drift         : 3134.978
 ARIMA(0,1,0)                               : 3142.742
 ARIMA(1,1,0)            with drift         : 3141.808
 ARIMA(1,1,0)(2,0,0)[12] with drift         : 3120.761
 ARIMA(1,1,0)(2,0,1)[12] with drift         : 3122.333
 ARIMA(1,1,0)(1,0,1)[12] with drift         : Inf
 ARIMA(0,1,0)(2,0,0)[12] with drift         : 3119.227
 ARIMA(0,1,0)(1,0,0)[12] with drift         : 3128.281
 ARIMA(0,1,0)(2,0,1)[12] with drift         : 3121.207
 ARIMA(0,1,0)(1,0,1)[12] with drift         : 3120.316
 ARIMA(0,1,1)(2,0,0)[12] with drift         : 3120.846
 ARIMA(1,1,1)(2,0,0)[12] with drift         : Inf
 ARIMA(0,1,0)(2,0,0)[12]                    : 3118.965
 ARIMA(0,1,0)(1,0,0)[12]                    : 3128.295
 ARIMA(0,1,0)(2,0,1)[12]                    : 3120.657
 ARIMA(0,1,0)(1,0,1)[12]                    : Inf
 ARIMA(1,1,0)(2,0,0)[12]                    : 3120.213
 ARIMA(0,1,1)(2,0,0)[12]                    : 3120.35
 ARIMA(1,1,1)(2,0,0)[12]                    : Inf

 Best model: ARIMA(0,1,0)(2,0,0)[12]                    
checkresiduals(rent_sarima)

Best rent SARIMA: nonseasonal first difference, seasonal AR(2), no drift. This is a parsimonous model with white noise residuals. Adding drift, nonseasonal AR(1) and seasonal MA(1) only slightly increase AICc. RMSE 31.8, MAE 24,08806, MAPE 0.7533599.

inventory_lambda = 0  # Note: As determined in Wesley's code, we will just use a log transform for the inventory series.
inventory_sarima <- auto.arima(inventory_train,lambda=inventory_lambda,trace=TRUE)

 ARIMA(2,1,2)(1,1,1)[12]                    : -380.197
 ARIMA(0,1,0)(0,1,0)[12]                    : -369.8573
 ARIMA(1,1,0)(1,1,0)[12]                    : -384.1577
 ARIMA(0,1,1)(0,1,1)[12]                    : -388.6616
 ARIMA(0,1,1)(0,1,0)[12]                    : -369.3899
 ARIMA(0,1,1)(1,1,1)[12]                    : -386.5875
 ARIMA(0,1,1)(0,1,2)[12]                    : -386.5754
 ARIMA(0,1,1)(1,1,0)[12]                    : -384.1396
 ARIMA(0,1,1)(1,1,2)[12]                    : -384.4064
 ARIMA(0,1,0)(0,1,1)[12]                    : -390.7689
 ARIMA(0,1,0)(1,1,1)[12]                    : -388.737
 ARIMA(0,1,0)(0,1,2)[12]                    : -388.7251
 ARIMA(0,1,0)(1,1,0)[12]                    : -385.7352
 ARIMA(0,1,0)(1,1,2)[12]                    : -386.5969
 ARIMA(1,1,0)(0,1,1)[12]                    : -388.6629
 ARIMA(1,1,1)(0,1,1)[12]                    : -386.6616

 Best model: ARIMA(0,1,0)(0,1,1)[12]                    
checkresiduals(inventory_sarima)

    Ljung-Box test

data:  Residuals from ARIMA(0,1,0)(0,1,1)[12]
Q* = 29.482, df = 23, p-value = 0.1649

Model df: 1.   Total lags used: 24

summary(inventory_sarima)
Series: inventory_train 
ARIMA(0,1,0)(0,1,1)[12] 
Box Cox transformation: lambda= 0 

Coefficients:
         sma1
      -0.5795
s.e.   0.1068

sigma^2 = 0.00147:  log likelihood = 197.44
AIC=-390.88   AICc=-390.77   BIC=-385.52

Training set error measures:
                   ME     RMSE      MAE        MPE     MAPE      MASE        ACF1
Training set -11.1729 673.5361 481.7169 0.04558128 2.686797 0.2295113 -0.01625024

Best inventory SARIMA: nonseasonal first difference, seasonal first-differenced MA(1). Residuals are mostly white noise - possibly some quarterly / two-year autocorrelations, but might also just be multiple testing (Ljung-Box doesn’t reject). Similarly to above, there are a number of models with very close AICc, though none with drift. RMSE 673.5361, MAE 481.7169, MAPE 2.686797.

ETS


rent_ets <- ets(rent_train, lambda = rent_lambda)  # note that we get AAA without lambda
summary(rent_ets)
ETS(A,Ad,A) 

Call:
 ets(y = rent_train, lambda = rent_lambda) 

  Box-Cox transformation: lambda= 1.9999 

  Smoothing parameters:
    alpha = 0.9996 
    beta  = 2e-04 
    gamma = 3e-04 
    phi   = 0.9744 

  Initial states:
    l = 3945991.9135 
    b = 60001.9011 
    s = -61679.68 -540.6751 47843.86 146169.6 45911.49 43593.8
           85571.15 74948.37 20094.2 -137340.3 -123001.8 -141570.1

  sigma:  100911

     AIC     AICc      BIC 
3386.294 3393.000 3436.618 

Training set error measures:
                   ME     RMSE      MAE          MPE      MAPE     MASE      ACF1
Training set 0.194433 29.52856 22.48471 -0.003752955 0.7053597 0.241986 0.1618703
checkresiduals(rent_ets)

    Ljung-Box test

data:  Residuals from ETS(A,Ad,A)
Q* = 37.477, df = 7, p-value = 3.809e-06

Model df: 17.   Total lags used: 24

inventory_ets <- ets(inventory_train)  # don't force lambda, because that forces additive model
summary(inventory_ets)
ETS(M,Ad,M) 

Call:
 ets(y = inventory_train) 

  Smoothing parameters:
    alpha = 0.9884 
    beta  = 0.0875 
    gamma = 1e-04 
    phi   = 0.98 

  Initial states:
    l = 13214.4939 
    b = 140.4349 
    s = 0.856 0.9087 0.9799 0.991 1.1088 1.146
           1.1204 1.0792 1.0181 0.9735 0.9027 0.9156

  sigma:  0.0367

     AIC     AICc      BIC 
2155.190 2161.895 2205.514 

Training set error measures:
                    ME     RMSE      MAE        MPE     MAPE      MASE       ACF1
Training set -26.60746 621.3574 469.1133 -0.1277442 2.705515 0.2235064 0.02335815
checkresiduals(inventory_ets)

    Ljung-Box test

data:  Residuals from ETS(M,Ad,M)
Q* = 27.818, df = 7, p-value = 0.0002373

Model df: 17.   Total lags used: 24

Rent ETS: AAA (additive error, trend, seasonality). alpha = 0.9996 beta = 2e-04 gamma = 3e-04 phi = 0.9744 Dominated by error, with very little trend or seasonality. Only slightly damped. Residuals mostly look like white noise, maybe 1.5 year seasonality, but Ljung-Box rejects at high significance level. Training RMSE 29.52856, MAE 22.48471, MAPE 0.7053597.

Inventory ETS: MAM (multiplicative error, additive trend, multiplicative seasonality). alpha = 0.9884 beta = 0.0875 gamma = 1e-04 phi = 0.98 Dominated by error, with some trend contribution and very little seasonality. Only slightly damped. Residuals mostly look like white noise but Ljung-Box rejects at high significance level. RMSE 621.3574, MAE 469.1133, MAPE 2.705515.

Cross-validation

source('cross_validation.R')
# rent
output_arima <- cross.validate(rent_SARIMA_model,rent_train,80,12)
output_ets <- cross.validate(rent_ets_model,rent_train,80,12)
generate.plots(output_arima,output_ets)

# save
arima_expanding_errors <- data.frame(matrix(unlist(output_arima[[2]]), ncol = 12, byrow = TRUE))
ets_expanding_errors <- data.frame(matrix(unlist(output_ets[[2]]), ncol = 12, byrow = TRUE))
rent_arima_expanding_rmse <- sqrt(rowMeans((arima_expanding_errors)**2,na.rm=TRUE))
rent_ets_expanding_rmse <- sqrt(rowMeans((ets_expanding_errors)**2,na.rm=TRUE))
#save(rent_arima_expanding_rmse,file='data/rent_arima_expanding_rmse.RData')
#save(rent_ets_expanding_rmse,file='data/rent_ets_expanding_rmse.RData')
# inventory
output_arima <- cross.validate(inventory_SARIMA_model,inventory_train,80,12)
output_ets <- cross.validate(inventory_ets_model,inventory_train,80,12)
generate.plots(output_arima,output_ets)

# save
arima_expanding_errors <- data.frame(matrix(unlist(output_arima[[2]]), ncol = 12, byrow = TRUE))
ets_expanding_errors <- data.frame(matrix(unlist(output_ets[[2]]), ncol = 12, byrow = TRUE))
inventory_arima_expanding_rmse <- sqrt(rowMeans((arima_expanding_errors)**2,na.rm=TRUE))
inventory_ets_expanding_rmse <- sqrt(rowMeans((ets_expanding_errors)**2,na.rm=TRUE))
save(inventory_arima_expanding_rmse,file='data/inventory_arima_expanding_rmse.RData')
save(inventory_ets_expanding_rmse,file='data/inventory_ets_expanding_rmse.RData')

Intervention analysis

# rent ARIMA
rent_covid <- ts(median_rent_manhattan[(length(rent_train)+1):length(median_rent_manhattan)],frequency=12,start=c(2020,2))
plot(forecast(rent_sarima,length(rent_covid)))
lines(rent_covid)

rent_effect <- rent_covid-forecast(rent_sarima,length(rent_covid))$mean
upper_conf <- forecast(rent_sarima,length(rent_covid))$upper[,2]-forecast(rent_sarima,length(rent_covid))$mean
lower_conf <- forecast(rent_sarima,length(rent_covid))$lower[,2]-forecast(rent_sarima,length(rent_covid))$mean
# plot(rent_effect,main='Estimated COVID Effect on Rent (via ARIMA)')
# lines(upper_conf,col='blue')
# lines(lower_conf,col='blue')
# abline(h=0)
# mean(rent_effect)


autoplot(rent_effect, main = "Estimated COVID Effect on Rent (via ARIMA)", ylab="Rent Effect", xlab="Year") + 
  geom_hline(yintercept=0, size=0.5, linetype=1) +
  theme_classic() +
  theme(legend.position="none") +
  scale_y_continuous(breaks=c(seq(-1000,600,200))) + 
  geom_hline(yintercept=seq(-1000, 600, by=200), size=0.1, linetype=2) +
  labs(caption = "Effect in relation to pre-COVID model predictions.")

ggsave("rent_covid_effect.png", width=128, height=96, units="mm")

NA
# inventory ARIMA
inventory_covid <- ts(inventory_manhattan[(length(rent_train)+1):length(inventory_manhattan)],frequency=12,start=c(2020,2))
plot(forecast(inventory_sarima,length(inventory_covid)))
lines(inventory_covid)

inventory_effect <- inventory_covid-forecast(inventory_sarima,length(inventory_covid))$mean
upper_conf <- forecast(inventory_sarima,length(inventory_covid))$upper[,2]-forecast(inventory_sarima,length(inventory_covid))$mean
# plot(inventory_effect,main='Estimated COVID Effect on Inventory (via ARIMA)')
# lines(upper_conf,col='blue')
# abline(h=0)
# mean(inventory_effect)

autoplot(inventory_effect, main = "Estimated COVID Effect on Inventory (via ARIMA)", ylab="Inventory Effect", xlab="Year") + 
  geom_hline(yintercept=0, size=0.5, linetype=1) +
  theme_classic() +
  theme(legend.position="none") +
  scale_y_continuous(breaks=c(seq(-6000,26000,6000))) + 
  geom_hline(yintercept=seq(-6000, 26000, by=6000), size=0.1, linetype=2) +
  labs(caption = "Effect in relation to pre-COVID model predictions.")

ggsave("inventory_covid_effect.png", width=128, height=96, units="mm")

# rent ETS
plot(forecast(rent_ets,length(rent_covid)))
lines(rent_covid)

rent_effect_ets <- rent_covid-forecast(rent_ets,length(rent_covid))$mean
upper_conf <- forecast(rent_ets,length(rent_covid))$upper[,2]-forecast(rent_ets,length(rent_covid))$mean
lower_conf <- forecast(rent_ets,length(rent_covid))$lower[,2]-forecast(rent_ets,length(rent_covid))$mean
plot(rent_effect_ets,main='Estimated COVID Effect on Rent (via ETS)')
lines(upper_conf,col='blue')
lines(lower_conf,col='blue')
abline(h=0)

mean(rent_effect_ets)
[1] -149.7264
# inventory ETS
plot(forecast(inventory_ets,length(inventory_covid)))
lines(inventory_covid)

inventory_effect_ets <- inventory_covid-forecast(inventory_ets,length(inventory_covid))$mean
upper_conf <- forecast(inventory_ets,length(inventory_covid))$upper[,2]-forecast(inventory_ets,length(inventory_covid))$mean
plot(inventory_effect_ets,main='Estimated COVID Effect on Inventory (via ETS)')
lines(upper_conf,col='blue')
abline(h=0)

mean(rent_effect_ets)
[1] -149.7264
intervention_vec <- rep(c(0,1), c(122, 34))
# allow free estimation
summary(auto.arima(median_rent_manhattan,xreg=intervention_vec,lambda=rent_lambda))
Series: median_rent_manhattan 
Regression with ARIMA(5,1,0)(2,0,1)[12] errors 
Box Cox transformation: lambda= 1.999924 

Coefficients:
         ar1     ar2     ar3      ar4     ar5    sar1    sar2     sma1      xreg
      0.2393  0.3747  0.2056  -0.2273  0.1387  0.5785  0.2231  -0.5980  199019.2
s.e.  0.0824  0.0856  0.0895   0.0865  0.0859  0.2305  0.1110   0.2299  116431.3

sigma^2 = 1.848e+10:  log likelihood = -2049.35
AIC=4118.69   AICc=4120.22   BIC=4149.13

Training set error measures:
                  ME     RMSE      MAE        MPE      MAPE      MASE       ACF1
Training set 2.82655 40.17769 31.31056 0.09214803 0.9687078 0.1408447 0.02401528
summary(auto.arima(inventory_manhattan,xreg=intervention_vec,lambda=inventory_lambda))
Series: inventory_manhattan 
Regression with ARIMA(2,1,0)(2,0,0)[12] errors 
Box Cox transformation: lambda= 0 

Coefficients:
         ar1     ar2    sar1    sar2     xreg
      0.3703  0.2798  0.2748  0.4197  -0.2299
s.e.  0.0897  0.1001  0.0707  0.0732   0.0642

sigma^2 = 0.003774:  log likelihood = 210.95
AIC=-409.91   AICc=-409.34   BIC=-391.65

Training set error measures:
                    ME     RMSE      MAE        MPE     MAPE      MASE      ACF1
Training set -92.25497 1222.635 796.6559 -0.2457241 4.167508 0.1608239 0.1695361
# specify models as above
summary(Arima(median_rent_manhattan,c(0,1,0),c(2,0,0),xreg=intervention_vec,lambda=rent_lambda))
Series: median_rent_manhattan 
Regression with ARIMA(0,1,0)(2,0,0)[12] errors 
Box Cox transformation: lambda= 1.999924 

Coefficients:
         sar1    sar2       xreg
      -0.0500  0.1024  -17881.39
s.e.   0.0945  0.1094  171662.35

sigma^2 = 3.005e+10:  log likelihood = -2088.34
AIC=4184.67   AICc=4184.94   BIC=4196.85

Training set error measures:
                   ME     RMSE      MAE       MPE     MAPE      MASE      ACF1
Training set 8.724534 50.98727 34.51863 0.2374683 1.053643 0.1552756 0.3987022
summary(Arima(inventory_manhattan,order=c(0,1,0),seasonal=c(0,1,1),xreg=intervention_vec,lambda=inventory_lambda))
Series: inventory_manhattan 
Regression with ARIMA(0,1,0)(0,1,1)[12] errors 
Box Cox transformation: lambda= 0 

Coefficients:
         sma1     xreg
      -0.8952  -0.1444
s.e.   0.1001   0.0695

sigma^2 = 0.004918:  log likelihood = 168.89
AIC=-331.78   AICc=-331.61   BIC=-322.89

Training set error measures:
                   ME     RMSE      MAE        MPE     MAPE      MASE      ACF1
Training set 5.469475 1579.887 841.8457 -0.1534565 3.995638 0.1699466 0.7245131
LS0tCnRpdGxlOiAiUmVudCBhbmQgSW52ZW50b3J5IFNBUklNQSBhbmQgRVRTIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0Kcm0obGlzdD1scygpKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0c2VyaWVzKQpsaWJyYXJ5KGZvcmVjYXN0KQpsaWJyYXJ5KFRTQSkKbGlicmFyeShkcGx5cikKYGBgCgojIyMgTG9hZCBhbmQgaW5zcGVjdCBkYXRhCgpgYGB7cn0KIyBzb3VyY2U6IGh0dHBzOi8vc3RyZWV0ZWFzeS5jb20vYmxvZy9kYXRhLWRhc2hib2FyZC8/YWdnPVRvdGFsJm1ldHJpYz1JbnZlbnRvcnkmdHlwZT1SZW50YWxzJmJlZHJvb21zPUFueSUyMEJlZHJvb21zJnByb3BlcnR5PUFueSUyMFByb3BlcnR5JTIwVHlwZSZtaW5EYXRlPTIwMTAtMDEtMDEmbWF4RGF0ZT0yMDIyLTEyLTAxJmFyZWE9RmxhdGlyb24sQnJvb2tseW4lMjBIZWlnaHRzCgojIG5vdGUgdGhhdCB3ZSBjYW4gYWxzbyBnZXQgYnJlYWtkb3duIGJ5IGJlZHJvb21zIGlmIHdlIHdhbnQKCiMgc2V0IHVwIGdlbmVyYWxpemFibGUgcGF0aCB3aXRob3V0IGxvY2FsIHBhdGggaGFyZCBjb2RlZApiYXNlX3BhdGggPC0gc3lzdGVtKCdnaXQgcmV2LXBhcnNlIC0tc2hvdy10b3BsZXZlbCcsIGludGVybiA9IFQpCgojIHJlbnRhbCBpbmRleCAtIG9uZSBmb3IgZWFjaCBvZiBmb3VyIHJlYWwgYm9yb3VnaHMKIyBub3RlOiB3ZSB3b24ndCB1c2UgdGhlIHJlbnRhbCBpbmRleCBhcyBpdCBpcyBhbHJlYWR5IHNtb290aGVkIHZpYSBBUklNQQpyZW50YWxfaW5kZXhfcmF3IDwtIHJlYWQuY3N2KHBhc3RlMChiYXNlX3BhdGgsICcvZGF0YS9yZW50YWxJbmRleF9BbGwuY3N2JykpCiMgbWVkaWFuIGFza2luZyByZW50IC0gZm91ciByZWFsIGJvcm91Z2hzICsgMTU1IG5laWdoYm9yaG9vZHMKbWVkaWFuX3JlbnRfcmF3IDwtIHJlYWQuY3N2KHBhc3RlMChiYXNlX3BhdGgsICcvZGF0YS9tZWRpYW5Bc2tpbmdSZW50X0FsbC5jc3YnKSkKIyByZW50YWwgaW52ZW50b3J5IC0gZm91ciByZWFsIGJvcm91Z2hzICsgMTU1IG5laWdoYm9yaG9vZHMKaW52ZW50b3J5X3JhdyA8LSByZWFkLmNzdihwYXN0ZTAoYmFzZV9wYXRoLCAnL2RhdGEvcmVudGFsSW52ZW50b3J5X0FsbC5jc3YnKSkKCmBgYAoKYGBge3J9CnJlbnRhbF9pbmRleF9tYW5oYXR0YW4gPC0gdHMocmVudGFsX2luZGV4X3JhdyRNYW5oYXR0YW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnQ9YygyMDA3LDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZyZXF1ZW5jeT0xMikKCnRzZGlzcGxheShyZW50YWxfaW5kZXhfbWFuaGF0dGFuKQoKbWVkaWFuX3JlbnRfbWFuaGF0dGFuIDwtIG1lZGlhbl9yZW50X3JhdyAlPiUgCiAgZmlsdGVyKGFyZWFOYW1lPT0iTWFuaGF0dGFuIikgJT4lCiAgdW5saXN0KCkgJT4lIGFzLm51bWVyaWMoKSAlPiUgCiAgbmEub21pdCgpICU+JSAKICB0cyhzdGFydD1jKDIwMTAsMSksZnJlcXVlbmN5PTEyKQoKdHNkaXNwbGF5KG1lZGlhbl9yZW50X21hbmhhdHRhbikKCmludmVudG9yeV9tYW5oYXR0YW4gPC0gaW52ZW50b3J5X3JhdyAlPiUgCiAgZmlsdGVyKGFyZWFOYW1lPT0iTWFuaGF0dGFuIikgJT4lIAogIHVubGlzdCgpICU+JSBhcy5udW1lcmljKCkgJT4lIAogIG5hLm9taXQoKSAlPiUKICB0cyhzdGFydD1jKDIwMTAsMSksZnJlcXVlbmN5PTEyKQoKdHNkaXNwbGF5KGludmVudG9yeV9tYW5oYXR0YW4pCgpyZW50X3RyYWluIDwtIHdpbmRvdyhtZWRpYW5fcmVudF9tYW5oYXR0YW4sIHN0YXJ0PTIwMTAsIGVuZD1jKDIwMjAsMSkpCmludmVudG9yeV90cmFpbiA8LSB3aW5kb3coaW52ZW50b3J5X21hbmhhdHRhbiwgc3RhcnQ9MjAxMCwgZW5kPWMoMjAyMCwxKSkKcmVudF90ZXN0IDwtIHdpbmRvdyhtZWRpYW5fcmVudF9tYW5oYXR0YW4sIHN0YXJ0PWMoMjAyMCwyKSwgZW5kPWMoMjAyMiwxMikpCmludmVudG9yeV90ZXN0IDwtIHdpbmRvdyhpbnZlbnRvcnlfbWFuaGF0dGFuLCBzdGFydD1jKDIwMjAsMiksIGVuZD1jKDIwMjIsMTIpKQoKIyBCb3gtQ294IHRyYW5zZm9ybWVkCnRzZGlzcGxheShCb3hDb3gocmVudF90cmFpbixsYW1iZGE9J2F1dG8nKSkKdHNkaXNwbGF5KEJveENveChpbnZlbnRvcnlfdHJhaW4sbGFtYmRhPSdhdXRvJykpCmBgYAoKCmBgYHtyIChQbG90IERhdGEpfQoKIyBNZWRpYW4gUmVudAphdXRvcGxvdChtZWRpYW5fcmVudF9tYW5oYXR0YW4sIG1haW4gPSAiTWVkaWFuIFJlbnQgLSBNYW5oYXR0YW4iLCB5bGFiPSJNZWRpYW4gUmVudCAoJCkiLCB4bGFiPSJZZWFyIikgKyAKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPWMoc2VxKDMwMDAsNDIwMCw0MDApKSkgKyAKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9c2VxKDI4MDAsNDIwMCwgYnk9MjAwKSwgc2l6ZT0wLjEsIGxpbmV0eXBlPTIpCgpnZ3NhdmUoInJlbnRfcmF3X3Nlcmllcy5wbmciLCB3aWR0aD0xMjgsIGhlaWdodD05NiwgdW5pdHM9Im1tIikKCiMgSW52ZW50b3J5CmF1dG9wbG90KGludmVudG9yeV9tYW5oYXR0YW4sIG1haW4gPSAiSW52ZW50b3J5IC0gTWFuaGF0dGFuIiwgeWxhYj0iSW52ZW50b3J5IiwgeGxhYj0iWWVhciIpICsgCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1jKHNlcSgxMDAwMCw0MDAwMCwxMDAwMCkpKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdD1zZXEoMTAwMDAsNDAwMDAsIGJ5PTEwMDAwKSwgc2l6ZT0wLjEsIGxpbmV0eXBlPTIpCgpnZ3NhdmUoImludmVudG9yeV9yYXdfc2VyaWVzLnBuZyIsIHdpZHRoPTEyOCwgaGVpZ2h0PTk2LCB1bml0cz0ibW0iKQoKYGBgCgoKCmBgYHtyfQojc2F2ZShpbnZlbnRvcnlfbWFuaGF0dGFuLGZpbGU9J2RhdGEvaW52ZW50b3J5X21hbmhhdHRhbi5SRGF0YScpCiNzYXZlKG1lZGlhbl9yZW50X21hbmhhdHRhbixmaWxlPSdkYXRhL21lZGlhbl9yZW50X21hbmhhdHRhbi5SRGF0YScpCmBgYAoKCmBgYHtyfQpwbG90KChyZW50X3RyYWluLW1lYW4ocmVudF90cmFpbikpL3ZhcihyZW50X3RyYWluKS8zMCkKbGluZXMoKGludmVudG9yeV90cmFpbi1tZWFuKGludmVudG9yeV90cmFpbikpL3ZhcihpbnZlbnRvcnlfdHJhaW4pLGNvbD0ncmVkJykKYGBgCgpgYGB7cn0KcmVudF9zdGF0aW9uYXJ5IDwtIGRpZmYoQm94Q294KHJlbnRfdHJhaW4sbGFtYmRhPTApLGRpZmZlcmVuY2VzPTEsbGFnPTEpCmludmVudG9yeV9zdGF0aW9uYXJ5IDwtIGRpZmYoQm94Q294KGludmVudG9yeV90cmFpbixsYW1iZGE9MCksZGlmZmVyZW5jZXM9MSxsYWc9MSkKCnBsb3QoKHJlbnRfc3RhdGlvbmFyeS1tZWFuKHJlbnRfc3RhdGlvbmFyeSkpL3ZhcihyZW50X3N0YXRpb25hcnkpKQpsaW5lcygoKGludmVudG9yeV9zdGF0aW9uYXJ5LW1lYW4oaW52ZW50b3J5X3N0YXRpb25hcnkpKSkvdmFyKGludmVudG9yeV9zdGF0aW9uYXJ5KSxjb2w9J3JlZCcpCmBgYAoKCiMjIyBTQVJJTUEKCmBgYHtyfQojIG1lZGlhbiByZW50IFNBUklNQQpyZW50X2xhbWJkYSA9IEJveENveC5sYW1iZGEocmVudF90cmFpbikKcmVudF9zYXJpbWEgPC0gYXV0by5hcmltYShyZW50X3RyYWluLGxhbWJkYT1yZW50X2xhbWJkYSx0cmFjZT1UUlVFKQpjaGVja3Jlc2lkdWFscyhyZW50X3NhcmltYSkKc3VtbWFyeShyZW50X3NhcmltYSkKYGBgCkJlc3QgcmVudCBTQVJJTUE6IG5vbnNlYXNvbmFsIGZpcnN0IGRpZmZlcmVuY2UsIHNlYXNvbmFsIEFSKDIpLCBubyBkcmlmdC4gVGhpcyBpcyBhIHBhcnNpbW9ub3VzIG1vZGVsIHdpdGggd2hpdGUgbm9pc2UgcmVzaWR1YWxzLiBBZGRpbmcgZHJpZnQsIG5vbnNlYXNvbmFsIEFSKDEpIGFuZCBzZWFzb25hbCBNQSgxKSBvbmx5IHNsaWdodGx5IGluY3JlYXNlIEFJQ2MuIFJNU0UgMzEuOCwgTUFFIDI0LDA4ODA2LCBNQVBFIDAuNzUzMzU5OS4KCgpgYGB7cn0KaW52ZW50b3J5X2xhbWJkYSA9IDAgICMgTm90ZTogQXMgZGV0ZXJtaW5lZCBpbiBXZXNsZXkncyBjb2RlLCB3ZSB3aWxsIGp1c3QgdXNlIGEgbG9nIHRyYW5zZm9ybSBmb3IgdGhlIGludmVudG9yeSBzZXJpZXMuCmludmVudG9yeV9zYXJpbWEgPC0gYXV0by5hcmltYShpbnZlbnRvcnlfdHJhaW4sbGFtYmRhPWludmVudG9yeV9sYW1iZGEsdHJhY2U9VFJVRSkKY2hlY2tyZXNpZHVhbHMoaW52ZW50b3J5X3NhcmltYSkKc3VtbWFyeShpbnZlbnRvcnlfc2FyaW1hKQpgYGAKCkJlc3QgaW52ZW50b3J5IFNBUklNQTogbm9uc2Vhc29uYWwgZmlyc3QgZGlmZmVyZW5jZSwgc2Vhc29uYWwgZmlyc3QtZGlmZmVyZW5jZWQgTUEoMSkuIFJlc2lkdWFscyBhcmUgbW9zdGx5IHdoaXRlIG5vaXNlIC0gcG9zc2libHkgc29tZSBxdWFydGVybHkgLyB0d28teWVhciBhdXRvY29ycmVsYXRpb25zLCBidXQgbWlnaHQgYWxzbyBqdXN0IGJlIG11bHRpcGxlIHRlc3RpbmcgKExqdW5nLUJveCBkb2Vzbid0IHJlamVjdCkuIFNpbWlsYXJseSB0byBhYm92ZSwgdGhlcmUgYXJlIGEgbnVtYmVyIG9mIG1vZGVscyB3aXRoIHZlcnkgY2xvc2UgQUlDYywgdGhvdWdoIG5vbmUgd2l0aCBkcmlmdC4gUk1TRSA2NzMuNTM2MSwgTUFFIDQ4MS43MTY5LCBNQVBFIDIuNjg2Nzk3LgoKCiMjIyBFVFMKCmBgYHtyfQoKcmVudF9ldHMgPC0gZXRzKHJlbnRfdHJhaW4sIGxhbWJkYSA9IHJlbnRfbGFtYmRhKSAgIyBub3RlIHRoYXQgd2UgZ2V0IEFBQSB3aXRob3V0IGxhbWJkYQpzdW1tYXJ5KHJlbnRfZXRzKQpjaGVja3Jlc2lkdWFscyhyZW50X2V0cykKCmludmVudG9yeV9ldHMgPC0gZXRzKGludmVudG9yeV90cmFpbikgICMgZG9uJ3QgZm9yY2UgbGFtYmRhLCBiZWNhdXNlIHRoYXQgZm9yY2VzIGFkZGl0aXZlIG1vZGVsCnN1bW1hcnkoaW52ZW50b3J5X2V0cykKY2hlY2tyZXNpZHVhbHMoaW52ZW50b3J5X2V0cykKYGBgClJlbnQgRVRTOiBBQUEgKGFkZGl0aXZlIGVycm9yLCB0cmVuZCwgc2Vhc29uYWxpdHkpLgogICAgYWxwaGEgPSAwLjk5OTYgCiAgICBiZXRhICA9IDJlLTA0IAogICAgZ2FtbWEgPSAzZS0wNCAKICAgIHBoaSAgID0gMC45NzQ0IApEb21pbmF0ZWQgYnkgZXJyb3IsIHdpdGggdmVyeSBsaXR0bGUgdHJlbmQgb3Igc2Vhc29uYWxpdHkuIE9ubHkgc2xpZ2h0bHkgZGFtcGVkLiBSZXNpZHVhbHMgbW9zdGx5IGxvb2sgbGlrZSB3aGl0ZSBub2lzZSwgbWF5YmUgMS41IHllYXIgc2Vhc29uYWxpdHksIGJ1dCBManVuZy1Cb3ggcmVqZWN0cyBhdCBoaWdoIHNpZ25pZmljYW5jZSBsZXZlbC4gVHJhaW5pbmcgUk1TRSAyOS41Mjg1NiwgTUFFIDIyLjQ4NDcxLCBNQVBFIDAuNzA1MzU5Ny4KCkludmVudG9yeSBFVFM6IE1BTSAobXVsdGlwbGljYXRpdmUgZXJyb3IsIGFkZGl0aXZlIHRyZW5kLCBtdWx0aXBsaWNhdGl2ZSBzZWFzb25hbGl0eSkuCiAgICBhbHBoYSA9IDAuOTg4NCAKICAgIGJldGEgID0gMC4wODc1IAogICAgZ2FtbWEgPSAxZS0wNCAKICAgIHBoaSAgID0gMC45OApEb21pbmF0ZWQgYnkgZXJyb3IsIHdpdGggc29tZSB0cmVuZCBjb250cmlidXRpb24gYW5kIHZlcnkgbGl0dGxlIHNlYXNvbmFsaXR5LiBPbmx5IHNsaWdodGx5IGRhbXBlZC4gUmVzaWR1YWxzIG1vc3RseSBsb29rIGxpa2Ugd2hpdGUgbm9pc2UgYnV0IExqdW5nLUJveCByZWplY3RzIGF0IGhpZ2ggc2lnbmlmaWNhbmNlIGxldmVsLiBSTVNFIDYyMS4zNTc0LCBNQUUgNDY5LjExMzMsIE1BUEUgMi43MDU1MTUuCgoKIyMjIENyb3NzLXZhbGlkYXRpb24KCmBgYHtyfQpzb3VyY2UoJ2Nyb3NzX3ZhbGlkYXRpb24uUicpCmBgYAoKYGBge3J9CiMgcmVudApvdXRwdXRfYXJpbWEgPC0gY3Jvc3MudmFsaWRhdGUocmVudF9TQVJJTUFfbW9kZWwscmVudF90cmFpbiw4MCwxMikKb3V0cHV0X2V0cyA8LSBjcm9zcy52YWxpZGF0ZShyZW50X2V0c19tb2RlbCxyZW50X3RyYWluLDgwLDEyKQpnZW5lcmF0ZS5wbG90cyhvdXRwdXRfYXJpbWEsb3V0cHV0X2V0cykKYGBgCgpgYGB7cn0KIyBzYXZlCmFyaW1hX2V4cGFuZGluZ19lcnJvcnMgPC0gZGF0YS5mcmFtZShtYXRyaXgodW5saXN0KG91dHB1dF9hcmltYVtbMl1dKSwgbmNvbCA9IDEyLCBieXJvdyA9IFRSVUUpKQpldHNfZXhwYW5kaW5nX2Vycm9ycyA8LSBkYXRhLmZyYW1lKG1hdHJpeCh1bmxpc3Qob3V0cHV0X2V0c1tbMl1dKSwgbmNvbCA9IDEyLCBieXJvdyA9IFRSVUUpKQpyZW50X2FyaW1hX2V4cGFuZGluZ19ybXNlIDwtIHNxcnQocm93TWVhbnMoKGFyaW1hX2V4cGFuZGluZ19lcnJvcnMpKioyLG5hLnJtPVRSVUUpKQpyZW50X2V0c19leHBhbmRpbmdfcm1zZSA8LSBzcXJ0KHJvd01lYW5zKChldHNfZXhwYW5kaW5nX2Vycm9ycykqKjIsbmEucm09VFJVRSkpCiNzYXZlKHJlbnRfYXJpbWFfZXhwYW5kaW5nX3Jtc2UsZmlsZT0nZGF0YS9yZW50X2FyaW1hX2V4cGFuZGluZ19ybXNlLlJEYXRhJykKI3NhdmUocmVudF9ldHNfZXhwYW5kaW5nX3Jtc2UsZmlsZT0nZGF0YS9yZW50X2V0c19leHBhbmRpbmdfcm1zZS5SRGF0YScpCmBgYAoKCmBgYHtyfQojIGludmVudG9yeQpvdXRwdXRfYXJpbWEgPC0gY3Jvc3MudmFsaWRhdGUoaW52ZW50b3J5X1NBUklNQV9tb2RlbCxpbnZlbnRvcnlfdHJhaW4sODAsMTIpCm91dHB1dF9ldHMgPC0gY3Jvc3MudmFsaWRhdGUoaW52ZW50b3J5X2V0c19tb2RlbCxpbnZlbnRvcnlfdHJhaW4sODAsMTIpCmdlbmVyYXRlLnBsb3RzKG91dHB1dF9hcmltYSxvdXRwdXRfZXRzKQpgYGAKCmBgYHtyfQojIHNhdmUKYXJpbWFfZXhwYW5kaW5nX2Vycm9ycyA8LSBkYXRhLmZyYW1lKG1hdHJpeCh1bmxpc3Qob3V0cHV0X2FyaW1hW1syXV0pLCBuY29sID0gMTIsIGJ5cm93ID0gVFJVRSkpCmV0c19leHBhbmRpbmdfZXJyb3JzIDwtIGRhdGEuZnJhbWUobWF0cml4KHVubGlzdChvdXRwdXRfZXRzW1syXV0pLCBuY29sID0gMTIsIGJ5cm93ID0gVFJVRSkpCmludmVudG9yeV9hcmltYV9leHBhbmRpbmdfcm1zZSA8LSBzcXJ0KHJvd01lYW5zKChhcmltYV9leHBhbmRpbmdfZXJyb3JzKSoqMixuYS5ybT1UUlVFKSkKaW52ZW50b3J5X2V0c19leHBhbmRpbmdfcm1zZSA8LSBzcXJ0KHJvd01lYW5zKChldHNfZXhwYW5kaW5nX2Vycm9ycykqKjIsbmEucm09VFJVRSkpCnNhdmUoaW52ZW50b3J5X2FyaW1hX2V4cGFuZGluZ19ybXNlLGZpbGU9J2RhdGEvaW52ZW50b3J5X2FyaW1hX2V4cGFuZGluZ19ybXNlLlJEYXRhJykKc2F2ZShpbnZlbnRvcnlfZXRzX2V4cGFuZGluZ19ybXNlLGZpbGU9J2RhdGEvaW52ZW50b3J5X2V0c19leHBhbmRpbmdfcm1zZS5SRGF0YScpCmBgYAoKCiMjIyBJbnRlcnZlbnRpb24gYW5hbHlzaXMKCmBgYHtyfQojIHJlbnQgQVJJTUEKcmVudF9jb3ZpZCA8LSB0cyhtZWRpYW5fcmVudF9tYW5oYXR0YW5bKGxlbmd0aChyZW50X3RyYWluKSsxKTpsZW5ndGgobWVkaWFuX3JlbnRfbWFuaGF0dGFuKV0sZnJlcXVlbmN5PTEyLHN0YXJ0PWMoMjAyMCwyKSkKcGxvdChmb3JlY2FzdChyZW50X3NhcmltYSxsZW5ndGgocmVudF9jb3ZpZCkpKQpsaW5lcyhyZW50X2NvdmlkKQpyZW50X2VmZmVjdCA8LSByZW50X2NvdmlkLWZvcmVjYXN0KHJlbnRfc2FyaW1hLGxlbmd0aChyZW50X2NvdmlkKSkkbWVhbgp1cHBlcl9jb25mIDwtIGZvcmVjYXN0KHJlbnRfc2FyaW1hLGxlbmd0aChyZW50X2NvdmlkKSkkdXBwZXJbLDJdLWZvcmVjYXN0KHJlbnRfc2FyaW1hLGxlbmd0aChyZW50X2NvdmlkKSkkbWVhbgpsb3dlcl9jb25mIDwtIGZvcmVjYXN0KHJlbnRfc2FyaW1hLGxlbmd0aChyZW50X2NvdmlkKSkkbG93ZXJbLDJdLWZvcmVjYXN0KHJlbnRfc2FyaW1hLGxlbmd0aChyZW50X2NvdmlkKSkkbWVhbgojIHBsb3QocmVudF9lZmZlY3QsbWFpbj0nRXN0aW1hdGVkIENPVklEIEVmZmVjdCBvbiBSZW50ICh2aWEgQVJJTUEpJykKIyBsaW5lcyh1cHBlcl9jb25mLGNvbD0nYmx1ZScpCiMgbGluZXMobG93ZXJfY29uZixjb2w9J2JsdWUnKQojIGFibGluZShoPTApCiMgbWVhbihyZW50X2VmZmVjdCkKCgphdXRvcGxvdChyZW50X2VmZmVjdCwgbWFpbiA9ICJFc3RpbWF0ZWQgQ09WSUQgRWZmZWN0IG9uIFJlbnQgKHZpYSBBUklNQSkiLCB5bGFiPSJSZW50IEVmZmVjdCIsIHhsYWI9IlllYXIiKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLCBzaXplPTAuNSwgbGluZXR5cGU9MSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9YyhzZXEoLTEwMDAsNjAwLDIwMCkpKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdD1zZXEoLTEwMDAsIDYwMCwgYnk9MjAwKSwgc2l6ZT0wLjEsIGxpbmV0eXBlPTIpICsKICBsYWJzKGNhcHRpb24gPSAiRWZmZWN0IGluIHJlbGF0aW9uIHRvIHByZS1DT1ZJRCBtb2RlbCBwcmVkaWN0aW9ucy4iKQoKZ2dzYXZlKCJyZW50X2NvdmlkX2VmZmVjdC5wbmciLCB3aWR0aD0xMjgsIGhlaWdodD05NiwgdW5pdHM9Im1tIikKICAKYGBgCgpgYGB7cn0KIyBpbnZlbnRvcnkgQVJJTUEKaW52ZW50b3J5X2NvdmlkIDwtIHRzKGludmVudG9yeV9tYW5oYXR0YW5bKGxlbmd0aChyZW50X3RyYWluKSsxKTpsZW5ndGgoaW52ZW50b3J5X21hbmhhdHRhbildLGZyZXF1ZW5jeT0xMixzdGFydD1jKDIwMjAsMikpCnBsb3QoZm9yZWNhc3QoaW52ZW50b3J5X3NhcmltYSxsZW5ndGgoaW52ZW50b3J5X2NvdmlkKSkpCmxpbmVzKGludmVudG9yeV9jb3ZpZCkKaW52ZW50b3J5X2VmZmVjdCA8LSBpbnZlbnRvcnlfY292aWQtZm9yZWNhc3QoaW52ZW50b3J5X3NhcmltYSxsZW5ndGgoaW52ZW50b3J5X2NvdmlkKSkkbWVhbgp1cHBlcl9jb25mIDwtIGZvcmVjYXN0KGludmVudG9yeV9zYXJpbWEsbGVuZ3RoKGludmVudG9yeV9jb3ZpZCkpJHVwcGVyWywyXS1mb3JlY2FzdChpbnZlbnRvcnlfc2FyaW1hLGxlbmd0aChpbnZlbnRvcnlfY292aWQpKSRtZWFuCiMgcGxvdChpbnZlbnRvcnlfZWZmZWN0LG1haW49J0VzdGltYXRlZCBDT1ZJRCBFZmZlY3Qgb24gSW52ZW50b3J5ICh2aWEgQVJJTUEpJykKIyBsaW5lcyh1cHBlcl9jb25mLGNvbD0nYmx1ZScpCiMgYWJsaW5lKGg9MCkKIyBtZWFuKGludmVudG9yeV9lZmZlY3QpCgphdXRvcGxvdChpbnZlbnRvcnlfZWZmZWN0LCBtYWluID0gIkVzdGltYXRlZCBDT1ZJRCBFZmZlY3Qgb24gSW52ZW50b3J5ICh2aWEgQVJJTUEpIiwgeWxhYj0iSW52ZW50b3J5IEVmZmVjdCIsIHhsYWI9IlllYXIiKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLCBzaXplPTAuNSwgbGluZXR5cGU9MSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3M9YyhzZXEoLTYwMDAsMjYwMDAsNjAwMCkpKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdD1zZXEoLTYwMDAsIDI2MDAwLCBieT02MDAwKSwgc2l6ZT0wLjEsIGxpbmV0eXBlPTIpICsKICBsYWJzKGNhcHRpb24gPSAiRWZmZWN0IGluIHJlbGF0aW9uIHRvIHByZS1DT1ZJRCBtb2RlbCBwcmVkaWN0aW9ucy4iKQoKZ2dzYXZlKCJpbnZlbnRvcnlfY292aWRfZWZmZWN0LnBuZyIsIHdpZHRoPTEyOCwgaGVpZ2h0PTk2LCB1bml0cz0ibW0iKQpgYGAKCmBgYHtyfQojIHJlbnQgRVRTCnBsb3QoZm9yZWNhc3QocmVudF9ldHMsbGVuZ3RoKHJlbnRfY292aWQpKSkKbGluZXMocmVudF9jb3ZpZCkKcmVudF9lZmZlY3RfZXRzIDwtIHJlbnRfY292aWQtZm9yZWNhc3QocmVudF9ldHMsbGVuZ3RoKHJlbnRfY292aWQpKSRtZWFuCnVwcGVyX2NvbmYgPC0gZm9yZWNhc3QocmVudF9ldHMsbGVuZ3RoKHJlbnRfY292aWQpKSR1cHBlclssMl0tZm9yZWNhc3QocmVudF9ldHMsbGVuZ3RoKHJlbnRfY292aWQpKSRtZWFuCmxvd2VyX2NvbmYgPC0gZm9yZWNhc3QocmVudF9ldHMsbGVuZ3RoKHJlbnRfY292aWQpKSRsb3dlclssMl0tZm9yZWNhc3QocmVudF9ldHMsbGVuZ3RoKHJlbnRfY292aWQpKSRtZWFuCnBsb3QocmVudF9lZmZlY3RfZXRzLG1haW49J0VzdGltYXRlZCBDT1ZJRCBFZmZlY3Qgb24gUmVudCAodmlhIEVUUyknKQpsaW5lcyh1cHBlcl9jb25mLGNvbD0nYmx1ZScpCmxpbmVzKGxvd2VyX2NvbmYsY29sPSdibHVlJykKYWJsaW5lKGg9MCkKbWVhbihyZW50X2VmZmVjdF9ldHMpCmBgYAoKCmBgYHtyfQojIGludmVudG9yeSBFVFMKcGxvdChmb3JlY2FzdChpbnZlbnRvcnlfZXRzLGxlbmd0aChpbnZlbnRvcnlfY292aWQpKSkKbGluZXMoaW52ZW50b3J5X2NvdmlkKQppbnZlbnRvcnlfZWZmZWN0X2V0cyA8LSBpbnZlbnRvcnlfY292aWQtZm9yZWNhc3QoaW52ZW50b3J5X2V0cyxsZW5ndGgoaW52ZW50b3J5X2NvdmlkKSkkbWVhbgp1cHBlcl9jb25mIDwtIGZvcmVjYXN0KGludmVudG9yeV9ldHMsbGVuZ3RoKGludmVudG9yeV9jb3ZpZCkpJHVwcGVyWywyXS1mb3JlY2FzdChpbnZlbnRvcnlfZXRzLGxlbmd0aChpbnZlbnRvcnlfY292aWQpKSRtZWFuCnBsb3QoaW52ZW50b3J5X2VmZmVjdF9ldHMsbWFpbj0nRXN0aW1hdGVkIENPVklEIEVmZmVjdCBvbiBJbnZlbnRvcnkgKHZpYSBFVFMpJykKbGluZXModXBwZXJfY29uZixjb2w9J2JsdWUnKQphYmxpbmUoaD0wKQptZWFuKHJlbnRfZWZmZWN0X2V0cykKYGBgCgoKYGBge3J9CmludGVydmVudGlvbl92ZWMgPC0gcmVwKGMoMCwxKSwgYygxMjIsIDM0KSkKIyBhbGxvdyBmcmVlIGVzdGltYXRpb24Kc3VtbWFyeShhdXRvLmFyaW1hKG1lZGlhbl9yZW50X21hbmhhdHRhbix4cmVnPWludGVydmVudGlvbl92ZWMsbGFtYmRhPXJlbnRfbGFtYmRhKSkKc3VtbWFyeShhdXRvLmFyaW1hKGludmVudG9yeV9tYW5oYXR0YW4seHJlZz1pbnRlcnZlbnRpb25fdmVjLGxhbWJkYT1pbnZlbnRvcnlfbGFtYmRhKSkKIyBzcGVjaWZ5IG1vZGVscyBhcyBhYm92ZQpzdW1tYXJ5KEFyaW1hKG1lZGlhbl9yZW50X21hbmhhdHRhbixjKDAsMSwwKSxjKDIsMCwwKSx4cmVnPWludGVydmVudGlvbl92ZWMsbGFtYmRhPXJlbnRfbGFtYmRhKSkKc3VtbWFyeShBcmltYShpbnZlbnRvcnlfbWFuaGF0dGFuLG9yZGVyPWMoMCwxLDApLHNlYXNvbmFsPWMoMCwxLDEpLHhyZWc9aW50ZXJ2ZW50aW9uX3ZlYyxsYW1iZGE9aW52ZW50b3J5X2xhbWJkYSkpCmBgYAoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoK